#include "Camera.h"
#include <QTime>
#include <QMatrix4x4>
#include <QtMath>

Camera::Camera()
    : m_cameraPos(0.0f, 0.0f, 3.0f),
      m_cameraFront(0.0f, 0.0f, -1.0f),
      m_cameraUp(0.0f, 1.0f, 0.0f),
      m_deltaTime(0.0f),
      m_lastFrame(currentFrame()),
      m_leftRight{0},
      m_upDown{0},
      m_yaw(-90.0f),
      m_pitch(0.0f),
      m_fov(45.0f),
      m_cameraSpeed(2.5f),
      m_mouseMoveSensitivity(0.1f)
{

}

QMatrix4x4 Camera::lookAt()
{
    const auto &cameraPos = updatePosition();
    const auto &cameraCenter = cameraPos + m_cameraFront;

    QMatrix4x4 view;
    view.lookAt(cameraPos, cameraCenter, m_cameraUp);
    return view;
}

QVector3D Camera::updatePosition()
{
    float currentFrame = this->currentFrame();
    m_deltaTime = currentFrame - m_lastFrame;
    m_lastFrame = currentFrame;

    return m_cameraPos -= (m_leftRight[0] * QVector3D::crossProduct(m_cameraFront, m_cameraUp)
            + m_upDown[0] * m_cameraFront).normalized() * m_cameraSpeed * m_deltaTime;
}

void Camera::onKeyPressed(int key)
{
    switch(key) {
    case Qt::Key_A: case Qt::Key_Left: pushDirectory(m_leftRight, -1); return;
    case Qt::Key_W: case Qt::Key_Up: pushDirectory(m_upDown, 1); return;
    case Qt::Key_D: case Qt::Key_Right: pushDirectory(m_leftRight, 1); return;
    case Qt::Key_S: case Qt::Key_Down: pushDirectory(m_upDown, -1); return;
    default: return;
    }
}

void Camera::onKeyReleased(int key)
{
    switch(key) {
    case Qt::Key_A: case Qt::Key_Left: popDirectory(m_leftRight, -1); return;
    case Qt::Key_W: case Qt::Key_Up: popDirectory(m_upDown, 1); return;
    case Qt::Key_D: case Qt::Key_Right: popDirectory(m_leftRight, 1); return;
    case Qt::Key_S: case Qt::Key_Down: popDirectory(m_upDown, -1); return;
    default: return;
    }
}

void Camera::onMouseMoved(const QPoint &offset)
{
    m_yaw += offset.x() * m_mouseMoveSensitivity;
    m_pitch -= offset.y() * m_mouseMoveSensitivity;

    if (m_pitch > 89.0f)
        m_pitch = 89.0f;
    else if (m_pitch < -89.0f)
        m_pitch = -89.0f;

    m_cameraFront.setX(qCos(qDegreesToRadians(m_yaw)) * qCos(qDegreesToRadians(m_pitch)));
    m_cameraFront.setY(qSin(qDegreesToRadians(m_pitch)));
    m_cameraFront.setZ(qSin(qDegreesToRadians(m_yaw)) * qCos(qDegreesToRadians(m_pitch)));
    m_cameraFront.normalize();
}

void Camera::onMouseWheelRotated(const QPoint &angleDelta)
{
    m_fov = qMax(1.0f, qMin(m_fov - angleDelta.y()/100.0f, 45.0f));
}

inline float Camera::currentFrame()
{
    return QTime::currentTime().msecsTo(QTime(0, 0)) / 1000.0f;
}

void Camera::pushDirectory(int array[2], int value)
{
    while (array[0] != value) {
        array[1] = array[0];
        array[0] = value;
    }
}

void Camera::popDirectory(int array[2], int value)
{
    int i = 0;
    while (array[i] != value)
        if (++i >= 2)
            return;
    while (i < 2-1)
        array[i++] = array[i+1];
    array[i] = 0;
}
